如果沒有碰過其他物件導向語言的話,Interface 應該會顯得很陌生,筆者剛學的時候也不例外,但其實它使用起來真的是很強大,本篇會介紹 Interface 的基本用法,在下個篇章會另外用一些設計模式,讓大家體會 Class 與 Interface 的實際運用。
Interface 被稱作介面或是接口,它主要在
在 Interface 中,會描述自身該有哪些 Method 或 Property ,但 Interface 不會也不能實作這些功能,僅僅是要求使用該 Interface 的 Class 得實現它,否則就會發生錯誤,無法正確編譯。
在宣告一個 Interface 時,直接使用 interface 做關鍵字宣告,這裡筆者習慣將 Interface 的命名前面都加上一個大寫的 I,避免和一般的 Class 搞混,但這是 C# 出身的習慣,如果是其他程式語言的 Interface 應該不會加上前綴 I,這裡大家可以斟酌看看。
首先在專案的目錄下建立新檔案 ICar.ts
,並在檔案裡宣告一個基本的 Interface,並將它做 export
:
interface ICar {
model: string;
move:() => void;
};
export default ICar;
Icar
中擁有兩個 Property,一個是 model
另一個是 Method move
,這代表如果有 Class 要實作這個接口,就必須將 model
與 move
都實作出來,否則就會錯誤,但要怎麼讓 Class 實作 Interface 呢?讓我們打開 src/Car.ts,其中有個 Class 叫做 Car
。
如果 Car
要實作 ICar
,只需要在 Class 的名稱後使用 implements
,並加上 Interface 的名稱:
import ICar from './ICar';
class Car implements ICar {
protected model: string = 'GQSM-X';
public color: string;
constructor(color: string) {
this.color = color;
}
public getDescription(): string {
return `型號是:${this.model}(${this.color})`;
}
}
加上後會發現原本正確的 Car
出現紅線警告:
這個錯誤的意思是 Car
既然實作了 ICar
,那就要好好遵守這個 Instance 需要的實現,然後下方會提示說有遺漏哪些 Property,在這個例子裡,被遺漏的 Property 是 move
,為了移除此錯誤,必須在 Car
裡實作 move
:
class Car implements ICar {
/* 其餘省略 */
public move(): string {
return '出發前進!';
}
}
加上 move
後,Car
下方的紅線並沒有消失,那是因為 Interface 所定義的 Property 都一定要是 Public,而在 Car
之中的 model
目前是 Protected 狀態,因此警告還是存在,並要求將 model
變更為 Public:
這個問題只要將 model
的 protected
改成 public
後就沒問題了,當然!如果 Class 沒有正確實現 Interface,TypeScript 的編譯也不會通過的。
另外,Car
不只實現了 Interface 內的行為,也有其他 Interface 內沒有描述到的 Method 和 Property,這是沒問題的,因為
而且,一個 Class 也能夠同時被多個 Interface 給約束,每個 Interface 間使用逗號間隔:
class Car implements ICarA, ICarB {
/**...**/
}
本節會簡單說明一下 Class 為何需要實現 Interface,以及基本的運用,下一章會再用設計模式講解一次。
假設今天有個 Method,我們將它放到 index.ts 中,它會接收 Car
來執行 move
這個動作,先到 Car.ts 中將 Car
做 export:
class Car implements ICar {
/* 其餘省略 */
}
export default Car;
打開 index.ts,將 Car
做 import
,並宣告一個執行 move
的方法 startMove
:
import Car from './Car';
const startMove = (car: Car): void => {
console.log(`開始${car.move()}`);
}
startMove(redCar); // 開始出發前進!
可以看見 startMove
接收了一個型別為 Car
的參數,代表我們可以將使用 Car
建立的 Instance 傳入執行。
但是會 move
的交通工具不只有車而已,除了車以外還有還有飛機 Airplane
讓我們替它建立一個新檔案 Airplane.ts
,並撰寫 Class 做 export
:
class Airplane {
public move(): string {
return '起飛出發!'
}
}
export default Airplane
如果一樣要讓 Airplane
執行 startMove
該怎麼做呢?再創建一個 airplaneStartMove
然後傳入 Airplane
型別的參數嗎?像這樣子:
const redCar = new Car('Red');
const airplane = new Airplane();
const startMove = (car: Car): void => {
console.log(`開始${car.move()}`);
}
const airplaneStartMove = (airplane: Airplane): void => {
console.log(`開始${airplane.move()}`);
}
startMove(redCar); // 開始出發前進!
airplaneStartMove(airplane); // 開始起飛出發!
明明是同樣都是 move
的行為,卻要寫兩個內容一模一樣,只是傳入參數型別不同的 Method 出來,而且除了車、飛機外,還可能會無限擴充出各種能夠移動的 Class,如果要為所有的 Class 都專寫一個 Method,這種程式太令人哭泣了。
Interface 就是為了這種情況出現了,它可以讓 Method 依賴 Interface 而不是 Class,例如說,今天我將 move
這個 Method 從 ICar
提出,並建立另一個檔案 IMove
,創建關於 move
這個行為的 IMove
:
interface IMove {
move(): void;
}
export default IMove;
接著到 Car
與 Airplane
的 Class,替它們加上 IMove
:
Car.ts
import IMove from './IMove';
class Car implements ICar, IMove {
/* 其餘省略 */
}
Airplane.ts
import IMove from './IMove';
class Airplane implements IMove {
/* 其餘省略 */
}
這時候因為 Car
與 Airplane
都實現了 IMove
,所以他們之中一定會有 move
這個 Method,所以在執行 move
時,就能夠不必對 Class,而是統一用 Interface 中執行就好,下方重新修改 startMove
:
import IMove from './IMove';
const startMove = (transportation: IMove): void => {
console.log(`開始${transportation.move()}`);
}
可以看見原本的參數型別從 Class 變成 Interface,如此一來,只要是實作該 Interface 的 Class 都可以傳入 startMove
裡執行:
const redCar = new Car('Red');
const airplane = new Airplane();
const startMove = (transportation: IMove): void => {
console.log(`開始${transportation.move()}`);
}
startMove(redCar); // 開始出發前進!
startMove(airplane); // 開始飛行前進!
到目前為止,昨天和今天兩篇文章就建立了兩個 Class 和兩個 Interface,如果全都放在根目錄中,會變得難以管理,因此在根目錄裡創建兩個資料夾,分別就是 class 與 interface,然後將 Class 全都丟到 class 目錄中,Interface 就放到 interface 裡管理:
|-class
|-Airplane.ts
|-Car.ts
|-interface
|-ICar.ts
|-IMove.ts
|-index.ts
|-index.js
別忘了移動完後,也要把程式中 import
的路徑修正,確認沒問題就可以執行 tsc index.ts
做編譯,如果成功編譯就能繼續輸入 node index.js
指令執行:
本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)
本篇簡單的介紹了 Interface 的基本用法以及使用情境,筆者認為
以文中的例子而言,如果所有的 Method 都以特定 Class 的實體作為參數傳入,那 Class 對於該 Method 的耦合度就太高了,因為這麼做等於綁定了只能透過某個 Class 才能執行的情境,但如果是以 Interface 定義的話,不論是哪個 Class ,只要它實作了指定的 Interface 就可以執行該 Method。
下一節會再繼續周旋在 Class 與 Interface 之間,以實作設計模式為主,希望能夠讓大家知道使用他們開發會多有趣靈活。
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!